/**
 * 
 */
package com.android.vocalNotes;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.format.DateUtils;
import android.util.Base64;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

/**
 * This class is the secondary activity. It is viewed when the user: - selects
 * an item on the main activity list, - chooses to edit an item on the main
 * activity list, - chooses to edit the item currently viewed in this activity,
 * - chooses to create a new vocal note from main activity, or - wants to import
 * a vocal note JSON file from outside this application.
 * 
 * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
 */
public class VocalNotesItemActivity extends Activity {

   /**
    * This private member stores the Intent object for this activity.
    */
   private Intent intent;

   /**
    * This private member stores the URI of the Intent.
    */
   private Uri uri;

   /**
    * This private member identifies a media player, responsible to play the
    * note audio.
    */
   private MediaPlayer mediaPlayer;

   /**
    * This private member identifies a media recorder, responsible to record the
    * note audio.
    */
   private MediaRecorder mediaRecorder;

   /**
    * This private member stores the recording state: - true : recording - false
    * : not recording
    */
   private boolean recordingState;

   /**
    * This private static member stores the name of the temporary audio file.
    */
   private static final String TEMP_FILE_NAME = "temp.amr";

   /**
    * This private static member stores the name of the temporary audio file for
    * saving state.
    */
   private static final String SAVED_FILE_NAME = "saved.amr";

   /**
    * This private member stores the path of the temporary recorded audio file.
    */
   private File tempAudioFile;

   /**
    * This private member stores the path of the saved temporary recorded audio
    * file.
    */
   private File savedAudioFile;

   /**
    * This private member identifies the handler responsible of managing
    * messages between the seekbar_thread and the seek bar.
    */
   private Handler progressHandler;

   /**
    * This private member identifies the secondary thread, responsible of
    * managing the progressing of the seek bar and the running time text view
    * during the playing of an audio note.
    */
   private Thread progressThread;

   /**
    * This private member identifies the Runnable object that will be executed
    * by the secondary thread.
    */
   private SecondaryRunCode secondaryRunCode;

   /**
    * This private member identifies the music note icon.
    */
   private ImageView musicNoteIcon;

   /**
    * This private member identifies the running time view.
    */
   private TextView runningTime;

   /**
    * This private member stores the starting time of the recording action.
    */
   private long startTime;

   /**
    * This private member identifies the total time View.
    */
   private TextView totalTime;

   /**
    * This private member identifies the record button.
    */
   private ImageButton recButton;

   /**
    * This private member identifies the rewind button.
    */
   private ImageButton rewindButton;

   /**
    * This private member identifies the stop button.
    */
   private ImageButton stopButton;

   /**
    * This private member identifies the play button.
    */
   private ImageButton playPauseButton;

   /**
    * This private member identifies the forward button.
    */
   private ImageButton forwardButton;

   /**
    * This private member determines the behavior of this activity when it
    * finishes. It is true when (on destroying) a "no-saved" toast must be
    * shown.
    */
   private boolean noSaved;

   /**
    * This private member determines, on resuming activity, whether or not
    * reconfiguring the media player.
    */
   private boolean resumeMediaPlayer;

   /**
    * This private member determines whether, on resuming this activity, media
    * player must be reconfigured with the temporary audio file or not when
    * resuming, depending on the last source played.
    */
   private boolean isLastSourceTemp;

   /**
    * This private member determines whether the media player must be retained
    * when some changes in the configuration state occur. If it is true then the
    * media player mustn't be stopped.
    */
   private boolean retainMediaPlayer;

   /**
    * This private member determines whether the media recorder must be retained
    * when some changes in the configuration state occur. If it is true then the
    * media recorder mustn't be stopped.
    */
   private boolean retainMediaRecorder;

   /**
    * This private member identifies the indeterminate seek bar.
    */
   private SeekBar indeterminateSeekBar;

   /**
    * This private member identifies the determinate seek bar.
    */
   private SeekBar determinateSeekBar;

   /**
    * This private member stores the imported data if any, -1 otherwise.
    */
   private long importedTimeStamp;

   /**
    * This private member stores the last media player position to resume after
    * another application activity comes in foreground and then this activity
    * returns to be visible.
    */
   private int lastPosition;

   /**
    * This method is responsible of initializing the activity at the creation
    * moment.
    * 
    * @param savedInstanceState
    *           A Bundle of information that describes the last state to reset.
    */
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // call super onCreate()
      super.onCreate(savedInstanceState);

      // check the external storage state: it must be mounted and writable
      String state = Environment.getExternalStorageState();
      if (!Environment.MEDIA_MOUNTED.equals(state)
            || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
         // if the external storage media is not mounted notify the user and
         // exit

         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.external_storage_error);

         // set not cancelable
         builder.setCancelable(false);

         // set listener for positive button
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                     // finish this activity
                     finish();
                  }
               });

         // create the alert dialog from the builder
         AlertDialog alert = builder.create();

         // show the alert dialog
         alert.show();

      }

      // store the Intent of this activity instance
      intent = getIntent();

      // store the URI of the Intent
      uri = intent.getData();

      // set the temporary audio file path
      tempAudioFile = new File(getExternalFilesDir(null), TEMP_FILE_NAME);

      // set the saved temporary audio file path
      savedAudioFile = new File(getExternalFilesDir(null), SAVED_FILE_NAME);

      // set the last source as the URI (later will be set correctly)
      isLastSourceTemp = false;

      // media player must not be resumed as default
      resumeMediaPlayer = false;

      // initialize the retain media recorder state
      retainMediaRecorder = false;

      // initialize the retain media player state
      retainMediaPlayer = false;

      // the imported time-stamp is -1 if it isn't an imported note
      importedTimeStamp = -1;

      // the last position is initially the start
      lastPosition = 0;

      // if the state was saved
      if (savedInstanceState != null) {
         // if was recording
         if (savedInstanceState.getBoolean("isRecording")) {
            // declare the media recorder to not be initialized
            retainMediaRecorder = true;

            // restore the start time
            startTime = savedInstanceState.getLong("startTime");

            // if wasn't recording
         } else {
            // declare the media recorder to be initialized
            retainMediaRecorder = false;
         }

         // declare the media player to be initialized or not
         retainMediaPlayer = savedInstanceState.getBoolean("isPlaying");

         // declare to configure media player with it or with default
         isLastSourceTemp = savedInstanceState.getBoolean("usingTemp");
      }

      // get the intent action
      String action = intent.getAction();

      // switch on the values of the action
      if (action.equals(Intent.ACTION_INSERT)) {
         // an insert action is for a new or an imported (JSON) vocal note
         onCreateNew();

      } else if (action.equals(Intent.ACTION_VIEW)) {
         // check the MIME type for distinguish an imported (JSON) vocal note
         if (!intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
            // it is an imported (JSON) vocal note
            onCreateNew();

         } else {
            // (else) it is a stored vocal note details
            onCreateView();
         }

      } else if (action.equals(Intent.ACTION_EDIT)) {
         // an edit action is for the editing of a stored vocal note
         onCreateEdit();

      } else {
         // the default case: other actions are not accepted, give an advise to
         // the user

         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.wrong_action);

         // set not cancelable
         builder.setCancelable(false);

         // set listener for positive button
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                     // finish this activity
                     finish();
                  }
               });
      }
   }

   /**
    * This method sets the content view of the activity in the case of an empty
    * new vocal note.
    */
   private void onCreateNew() {
      // set the content view of an empty vocal note
      setContentView(R.layout.newnote);

      // check the MIME type for distinguish an imported (JSON) vocal note
      if (!intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE)) {
         // the alert builder for notifying the user if something goes wrong
         AlertDialog.Builder builder;

         // the JSON object where to put the content of the JSON file
         JSONObject json = null;

         // the EditText view that will contain some fields.
         EditText et;

         // the RatingBar view that will contain the rating
         RatingBar rb;

         // the bytes that will contain the base64-decoded audio
         byte[] audioBytes;

         // the output stream for writing the base64-decoded audio file
         FileOutputStream audioDecodedOut;

         try {
            // get the JSON object for the JSON file
            json = VocalNotes.openJSONFile(getContentResolver(),
                  intent.getData());

            // set the title
            et = (EditText) findViewById(R.id.titletext);
            et.setText(json.getString(VocalNotes.OBJECT_NOTE_TITLE));

            // set the description
            et = (EditText) findViewById(R.id.descriptiontext);
            et.setText(json.getString(VocalNotes.OBJECT_NOTE_DESCR));

            // set the rating
            rb = (RatingBar) findViewById(R.id.ratingbar);
            rb.setRating((float) json.getInt(VocalNotes.OBJECT_NOTE_RATE));

            // import data and time
            importedTimeStamp = json.getLong(VocalNotes.OBJECT_NOTE_DATE);

            try {
               // get the base64-decoded audio bytes
               audioBytes = Base64.decode(
                     json.getString(VocalNotes.OBJECT_NOTE_AUDIO),
                     Base64.DEFAULT);

               // open the temporary (saved) audio file output stream
               // into which to write the base64-decoded audio
               audioDecodedOut = new FileOutputStream(savedAudioFile);

               // write the base64-decoded audio into the output stream
               audioDecodedOut.write(audioBytes);

               // close the output stream
               audioDecodedOut.close();

            } catch (IllegalArgumentException d) {
               // thrown if base64-decoding errors occur
               d.printStackTrace();

            } catch (FileNotFoundException f) {
               // thrown if errors occur while opening the temporary audio
               // output stream
               f.printStackTrace();

            } catch (IOException e) {
               // thrown if errors occur while writing into or closing the
               // output stream
               e.printStackTrace();
            }

         } catch (IllegalArgumentException e) {
            // thrown if the JSON file is not compatible with this application

            // create the builder of the alert dialog
            builder = new AlertDialog.Builder(this);

            // set the message
            builder.setMessage(R.string.json_uncompatible);

            // set not cancelable
            builder.setCancelable(false);

            // set listener for positive button
            builder.setPositiveButton(R.string.yes,
                  new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
                        // finish this activity
                        finish();
                     }
                  });

            // create the alert dialog from the builder
            AlertDialog alert = builder.create();

            // show the alert dialog
            alert.show();

         } catch (FileNotFoundException e) {
            // thrown if the JSON file cannot be opened

            // create the builder of the alert dialog
            builder = new AlertDialog.Builder(this);

            // set the message
            builder.setMessage(R.string.file_not_found);

            // set not cancelable
            builder.setCancelable(false);

            // set listener for positive button
            builder.setPositiveButton(R.string.yes,
                  new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
                        // finish this activity
                        finish();
                     }
                  });

            // create the alert dialog from the builder
            AlertDialog alert = builder.create();

            // show the alert dialog
            alert.show();

         } catch (JSONException j) {
            // thrown if errors occur while managing the JSON object
            j.printStackTrace();
         }
      }
   }

   /**
    * This method is responsible of setting the content view of the activity in
    * the case of viewing a given stored vocal note.
    */
   private void onCreateView() {
      // set the content view for this vocal note
      setContentView(R.layout.details);

      // query the content provider and get the cursor
      Cursor result = managedQuery(uri,
            VocalNotes.VocalNotesContentProviderAPI.PROJECTION, null, null,
            null);

      // if the URI is correct then the cursor must have only one item
      if (result.moveToFirst()) {
         // set the title
         TextView tv = (TextView) findViewById(R.id.titletext_detail);
         tv.setText(result.getString(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE)));

         // set the date and time of last modification
         tv = (TextView) findViewById(R.id.lastmodify_detail);
         tv.setText(android.text.format.DateFormat.format(
               "MMM dd, yyyy h:mmaa",
               result.getLong(result
                     .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE))));

         // set the description
         tv = (TextView) findViewById(R.id.descriptiontext_detail);
         tv.setText(result.getString(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION)));

         // set the rating bar
         RatingBar rb = (RatingBar) findViewById(R.id.ratingbar_detail);
         rb.setRating((float) result.getInt(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING)));

      } else {
         // if the cursor hasn't any item notify the user and finish

         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.wrong_uri);

         // set not cancelable
         builder.setCancelable(false);

         // set listener for positive button
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                     // finish this activity
                     finish();
                  }
               });
      }
   }

   /**
    * This method is responsible of setting the content view of the activity in
    * the case of the editing a given stored vocal note.
    */
   private void onCreateEdit() {
      // set the content view of an editable vocal note
      setContentView(R.layout.newnote);

      // query the content provider and get the cursor
      Cursor result = managedQuery(uri,
            VocalNotes.VocalNotesContentProviderAPI.PROJECTION, null, null,
            null);

      // if the URI is correct then the cursor must have only one item
      if (result.moveToFirst()) {
         // set the title
         EditText tv = (EditText) findViewById(R.id.titletext);
         tv.setText(result.getString(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE)));

         // set the description
         tv = (EditText) findViewById(R.id.descriptiontext);
         tv.setText(result.getString(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION)));

         // set the rating bar
         RatingBar rb = (RatingBar) findViewById(R.id.ratingbar);
         rb.setRating((float) result.getInt(result
               .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING)));

      } else {
         // if the cursor hasn't any item notify the user and finish

         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.wrong_uri);

         // set not cancelable
         builder.setCancelable(false);

         // set listener for positive button
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                     // finish this activity
                     finish();
                  }
               });
      }
   }

   /**
    * This method is responsible of setting this activity when it get focus.
    */
   @Override
   protected void onStart() {
      // call super onStart()
      super.onStart();

      // get the intent action
      String action = intent.getAction();

      // if media recorder is not retained
      if (!retainMediaRecorder) {
         // create a MediaRecorder object responsible of recording an audio
         // stream
         mediaRecorder = new MediaRecorder();
         // state of mediaRecorder: Initial

         // configure the media recorder
         configureMediaRecorder();
         // state of mediaRecorder: DataSourceConfigured

         // set the recording state to not recording (false)
         recordingState = false;

      } else {
         // get the retained media recorder
         mediaRecorder = (MediaRecorder) getLastNonConfigurationInstance();

         // set the recording state to recording (true)
         recordingState = true;

         // reset the declaration of retainment
         retainMediaRecorder = false;
      }

      // if media player is retained
      if (retainMediaPlayer) {
         // get the retained media player
         mediaPlayer = (MediaPlayer) getLastNonConfigurationInstance();

         // reset the declaration of retainment
         retainMediaPlayer = false;

         // rename it to restore its state
         savedAudioFile.renameTo(tempAudioFile);

         // and clear this information (used in onResume())
         isLastSourceTemp = false;

      } else {
         // create a MediaPlayer object responsible of playing an audio stream
         mediaPlayer = new MediaPlayer();
         // state of mediaPlayer: Idle

         // switch on the values of the action
         if (action.equals(Intent.ACTION_INSERT)) {
            // an insert action is for a new or an imported (JSON) vocal note

            // if before changes in the configuration occur, a temporary file
            // was used
            if (isLastSourceTemp) {
               // then rename it to restore its state
               savedAudioFile.renameTo(tempAudioFile);

               // and clear this information (used in onResume())
               isLastSourceTemp = false;
            }

            // check if media player can be configured with temporary audio file
            if (tempAudioFile.exists() && tempAudioFile.length() > 0) {
               // configure the media player with the temporary audio file
               configureMediaPlayer(tempAudioFile.getAbsolutePath());
               // state of mediaPlayer: Prepared
            }

         } else if (action.equals(Intent.ACTION_VIEW)) {
            // check the MIME type for distinguish an imported (JSON) vocal note
            if (!intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
               // import the audio file from json by renaming it
               savedAudioFile.renameTo(tempAudioFile);

               // configure the media player with the temporary audio file
               configureMediaPlayer(tempAudioFile.getAbsolutePath());
               // state of mediaPlayer: Prepared

            } else {
               // (else) it is a stored vocal note details

               // configure the media player with the URI
               configureMediaPlayer(null);
               // state of mediaPlayer: Prepared

            }

         } else if (action.equals(Intent.ACTION_EDIT)) {
            // an edit action is for the editing of a stored vocal note

            // if before changes in the configuration occur, a temporary file
            // was used
            if (isLastSourceTemp) {
               // then rename it to restore its state
               savedAudioFile.renameTo(tempAudioFile);

               // configure the media player with the temporary audio file
               configureMediaPlayer(tempAudioFile.getAbsolutePath());
               // state of mediaPlayer: Prepared

               // and clear this information (used in onResume())
               isLastSourceTemp = false;

            } else {
               // else configure the media player with the URI
               configureMediaPlayer(null);
               // state of mediaPlayer: Prepared
            }

         } else {
            // the default case: other actions are not accepted, give an advise
            // to the user

            // create the builder of the alert dialog
            AlertDialog.Builder builder = new AlertDialog.Builder(this);

            // set the message
            builder.setMessage(R.string.wrong_action);

            // set not cancelable
            builder.setCancelable(false);

            // set listener for positive button
            builder.setPositiveButton(R.string.ok,
                  new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
                        // finish this activity
                        finish();
                     }
                  });
         }
      }

      // the toast must be viewed if changes are made but not saved
      if (!intent.getAction().equals(Intent.ACTION_VIEW)
            || !intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
         // declare to view the toast
         noSaved = true;

      } else {
         // declare to not view the toast
         noSaved = false;
      }

      // configure the media controller
      initMediaController();

      // if recording
      if (recordingState) {

         // make secondaryRunCode executable
         secondaryRunCode.toStart();

         // set the record button pressed
         recButton.setColorFilter(Color.RED);

         // set the music note symbol like record button
         musicNoteIcon.setColorFilter(Color.RED);

         // set the rewind button disabled
         rewindButton.setEnabled(false);

         // set the play/pause button disabled
         playPauseButton.setEnabled(false);

         // set the forward button disabled
         forwardButton.setEnabled(false);

         // show the indeterminate seek bar
         indeterminateSeekBar.setVisibility(View.VISIBLE);

         // hide the determinate seek bar
         determinateSeekBar.setVisibility(View.GONE);

         // keep the screen on while recording (recording stops if the screen
         // goes off)
         indeterminateSeekBar.setKeepScreenOn(true);

         // else if playing
      } else if (mediaPlayer.isPlaying()) {

         // make secondaryRunCode executable
         secondaryRunCode.toStart();

         // set the "pause" icon
         playPauseButton.setImageResource(R.drawable.pause);

         // set the color to play/pause icon
         playPauseButton.setColorFilter(Color.GRAY);

         // set the color to music note icon
         musicNoteIcon.setColorFilter(Color.GREEN);

         // show the determinate seek bar
         determinateSeekBar.setVisibility(View.VISIBLE);

         // hide the indeterminate seek bar
         indeterminateSeekBar.setVisibility(View.GONE);

         // don't keep the screen on
         indeterminateSeekBar.setKeepScreenOn(false);

      } else {

         // set stop button not enabled
         stopButton.setEnabled(false);

      }

      // if viewing a store vocal note or importing one from a JSON file
      if (intent.getAction().equals(Intent.ACTION_VIEW)) {
         // hide the record button
         recButton.setEnabled(false);
      }

      // if from viewing details the user select to modify this vocal note
      // and then he modifies it, when returning to this activity
      // the text views must be updated because onCreate() is not
      // called, but only onStart() and onResume() are called
      // so reset the text in these text views:

      // if viewing a internally stored vocal note
      if (action.equals(Intent.ACTION_VIEW)
            && intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
         // query the content provider and get the cursor
         Cursor result = managedQuery(uri,
               VocalNotes.VocalNotesContentProviderAPI.PROJECTION, null, null,
               null);

         // if the URI is correct then the cursor must have only one item
         if (result.moveToFirst()) {
            // (re)set the title
            TextView tv = (TextView) findViewById(R.id.titletext_detail);
            tv.setText(result.getString(result
                  .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE)));

            // (re)set the date and time of last modification
            tv = (TextView) findViewById(R.id.lastmodify_detail);
            tv.setText(android.text.format.DateFormat.format(
                  "MMM dd, yyyy h:mmaa",
                  result.getLong(result
                        .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE))));

            // (re)set the description
            tv = (TextView) findViewById(R.id.descriptiontext_detail);
            tv.setText(result.getString(result
                  .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION)));

            // (re)set the rating bar
            RatingBar rb = (RatingBar) findViewById(R.id.ratingbar_detail);
            rb.setRating((float) result.getInt(result
                  .getColumnIndex(VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING)));
         }
      }
   }

   /**
    * This private method is responsible of configuring the media recorder.
    */
   private void configureMediaRecorder() {
      // reset the media recorder
      mediaRecorder.reset();
      // state of mediaRecorder: Initial

      // set the audio source (microphone)
      mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      // state of mediaRecorder: Initialized

      // set the output format
      mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
      // state of mediaRecorder: DataSourceConfigured

      // set the audio encoder
      mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
      // state of mediaRecorder: DataSourceConfigured

      // set the output file
      mediaRecorder.setOutputFile(tempAudioFile.getAbsolutePath());
      // state of mediaRecorder: DataSourceConfigured

      // prepare the media recorder
      try {
         mediaRecorder.prepare();
         // state of mediaRecorder: Prepared

      } catch (IllegalStateException e) {
         // thrown if media recorder was in an invalid state
         e.printStackTrace();

      } catch (IOException e) {
         // thrown if media recorder isn't able to write to file
         e.printStackTrace();
      }
      // state of mediaRecorder: Prepared
   }

   /**
    * This private member is responsible of configuring the media player.
    */
   private void configureMediaPlayer(String audio) {
      // reset the media player
      mediaPlayer.reset();
      // state of mediaPlayer: Idle

      // set the audio stream type to STREAM_MUSIC so that the external speaker
      // is used
      mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
      // state of mediaPlayer: Idle

      try {
         // set the data source
         if (audio == null) {
            mediaPlayer.setDataSource(this, uri);
         } else {
            mediaPlayer.setDataSource(audio);
         }
         // state of mediaPlayer: Initialized

         // prepare the media player asynchronously
         mediaPlayer.prepare();
         // state of mediaPlayer: Prepared

      } catch (IllegalArgumentException e) {
         // thrown if the data source is wrong
         e.printStackTrace();

      } catch (SecurityException e) {
         // thrown if the security manager check is failed
         e.printStackTrace();

      } catch (IllegalStateException e) {
         // thrown if the media player was in an invalid state
         e.printStackTrace();

      } catch (IOException e) {
         // thrown if media player isn't able to read from file
         e.printStackTrace();
      }
      // state of mediaPlayer: Prepared
   }

   /**
    * This private method is responsible of initializing all the ImageButton
    * variables.
    */
   private void initMediaController() {
      // get the music note icon
      musicNoteIcon = (ImageView) findViewById(R.id.icon);

      // set default color to music note icon
      musicNoteIcon.clearColorFilter();

      // get the running time view
      runningTime = (TextView) findViewById(R.id.running_time);

      // get the total time view
      totalTime = (TextView) findViewById(R.id.total_time);

      // get the record ImageButton
      recButton = (ImageButton) findViewById(R.id.record_button);

      // get the rewind ImageButton
      rewindButton = (ImageButton) findViewById(R.id.rewind_button);

      // get the stop ImageButton
      stopButton = (ImageButton) findViewById(R.id.stop_button);

      // get the play/pause ImageButton
      playPauseButton = (ImageButton) findViewById(R.id.play_pause_button);

      // set the "play" icon
      playPauseButton.setImageResource(R.drawable.play);

      // set the default color to play/pause icon
      playPauseButton.clearColorFilter();

      // get the forward ImageButton
      forwardButton = (ImageButton) findViewById(R.id.forward_button);

      // get the indeterminate seek bar
      indeterminateSeekBar = (SeekBar) findViewById(R.id.indeterminate_seek_bar);

      // get the determinate seek bar
      determinateSeekBar = (SeekBar) findViewById(R.id.determinate_seek_bar);

      // hide the thumb in the indeterminate seek bar
      indeterminateSeekBar.setThumb(null);

      // select the visible seek bar
      if (recordingState) {
         // is recording : show the indeterminate seek bar
         indeterminateSeekBar.setVisibility(View.VISIBLE);

         // hide the determinate seek bar
         determinateSeekBar.setVisibility(View.GONE);
      } else {
         // is not recording : show the determinate seek bar
         determinateSeekBar.setVisibility(View.VISIBLE);

         // hide the determinate seek bar
         indeterminateSeekBar.setVisibility(View.GONE);
      }

      // if creating a new vocal note and the audio is not recorder yet
      if (intent.getAction().equals(Intent.ACTION_INSERT)
            && tempAudioFile.length() == 0) {

         // set the running time to the start time of media player
         runningTime.setText(DateUtils.formatElapsedTime(0));

         // set the total time
         totalTime.setText(DateUtils.formatElapsedTime(0));

         // set the rewind button disabled
         rewindButton.setEnabled(false);

         // set the play/pause button disabled
         playPauseButton.setEnabled(false);

         // set the forward button disabled
         forwardButton.setEnabled(false);

         // set the maximum value of the determinate seek Bar
         determinateSeekBar.setMax(1);

         // set the initial position of the determinate seek bar to the current
         // position of media player
         determinateSeekBar.setProgress(0);

         // set the determinate seek bar not enabled
         determinateSeekBar.setEnabled(false);

      } else {

         // set the running time to the current time of media player
         runningTime.setText(DateUtils.formatElapsedTime(0));

         // set the total time
         totalTime.setText(DateUtils.formatElapsedTime(mediaPlayer
               .getDuration() / 1000));

         // set the maximum value of the determinate seek Bar
         determinateSeekBar.setMax(mediaPlayer.getDuration());

         // set the initial position of the determinate seek bar to the current
         // position of media player
         determinateSeekBar.setProgress(mediaPlayer.getCurrentPosition());

         // set the determinate seek bar enabled
         determinateSeekBar.setEnabled(true);
      }

      // set the seek bar user touch event :
      // media player has to be moved to the seek bar current position
      determinateSeekBar
            .setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
               @Override
               public void onProgressChanged(SeekBar seekbar, int progress,
                     boolean fromUser) {
                  // if the change comes from the user (while not recording)
                  if (fromUser) {
                     // user has touched the seek bar
                     mediaPlayer.seekTo(progress);

                     // set the running time to the current time of media player
                     runningTime.setText(DateUtils
                           .formatElapsedTime(progress / 1000));
                  }
               }

               @Override
               public void onStartTrackingTouch(SeekBar arg0) {
                  // nothing to do
               }

               @Override
               public void onStopTrackingTouch(SeekBar arg0) {
                  // nothing to do
               }
            });

      // create the secondary Runnable code
      secondaryRunCode = new SecondaryRunCode();

      // create the handler responsible of managing messages in the secondary
      // thread
      progressHandler = new SeekBarHandler();

      // create the thread that will manage the progress of the seek bar
      progressThread = new Thread(secondaryRunCode);

      // start the secondary thread
      progressThread.start();

      // set the listener on completion to media player
      mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
         @Override
         public void onCompletion(MediaPlayer mp) {
            // stop the media player
            mediaPlayer.stop();

            // stop the secondary thread
            secondaryRunCode.toStop();

            try {
               // re-prepare the media player
               mediaPlayer.prepare();
               // state of mediaPlayer: Prepared

            } catch (IllegalStateException e) {
               // thrown if media player was in an invalid state
               e.printStackTrace();

            } catch (IOException e) {
               // thrown if media player isn't able to read from file
               e.printStackTrace();
            }
            // state of mediaPlayer: Prepared

            // set the "play" icon
            playPauseButton.setImageResource(R.drawable.play);

            // set the default color to play/pause icon
            playPauseButton.clearColorFilter();

            // set default color to music note icon
            musicNoteIcon.clearColorFilter();

            // set the seek bar position to the current time of media player
            determinateSeekBar.setProgress(0);

            // set the running time to the current time of media player
            runningTime.setText(DateUtils.formatElapsedTime(0));

            // set stop button not enabled
            stopButton.setEnabled(false);
         }
      });

      // set a listener to the record ImageButton
      recButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
            // check the recording state
            if (recordingState) {
               // the media recorder is recording

               // so stop to record
               mediaRecorder.stop();
               // state of mediaRecorder: Initial

               // set recording state to not recording (false)
               recordingState = false;

               // stop the secondary thread
               secondaryRunCode.toStop();

               // reconfigure media player with new data source
               configureMediaPlayer(tempAudioFile.getAbsolutePath());

               // set the record button like not pressed
               recButton.clearColorFilter();

               // restore the music note icon color
               musicNoteIcon.clearColorFilter();

               // set the rewind button enabled
               rewindButton.setEnabled(true);

               // set the play/pause button enabled
               playPauseButton.setEnabled(true);

               // set the forward button enabled
               forwardButton.setEnabled(true);

               // set stop button not enabled
               stopButton.setEnabled(false);

               // set the running time to 0:00
               runningTime.setText(DateUtils.formatElapsedTime(0));

               // set the total time
               totalTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                     .getDuration() / 1000));

               // show the determinate seek bar
               determinateSeekBar.setVisibility(View.VISIBLE);

               // hide the indeterminate seek bar
               indeterminateSeekBar.setVisibility(View.GONE);

               // don't keep the screen on
               indeterminateSeekBar.setKeepScreenOn(false);

               // set the maximum value of the seekBar
               determinateSeekBar.setMax(mediaPlayer.getDuration());

               // set the seek bar position to the start
               determinateSeekBar.setProgress(0);

               // set the seek bar clickable
               determinateSeekBar.setEnabled(true);

            } else {
               // the media recorder is not recording

               // check if media player is playing
               if (mediaPlayer.isPlaying()) {
                  // if it is then stop it
                  mediaPlayer.stop();

                  // change the play/pause ImageButton image
                  playPauseButton.setImageResource(R.drawable.play);

                  // set the default color to play/pause icon
                  playPauseButton.clearColorFilter();

               } else {
                  // if not playing then start the worker thread

                  // make secondaryRunCode executable
                  secondaryRunCode.toStart();
               }

               // (re)configure media recorder
               configureMediaRecorder();
               // state of mediaRecorder: Prepared

               // so start recording
               mediaRecorder.start();
               // state of mediaRecorder: Recording

               // set the starting time to now
               startTime = System.currentTimeMillis();

               // set recording state to recording (true)
               recordingState = true;

               // set the record button pressed
               recButton.setColorFilter(Color.RED);

               // set the music note symbol like record button
               musicNoteIcon.setColorFilter(Color.RED);

               // set the rewind button disabled
               rewindButton.setEnabled(false);

               // set the play/pause button disabled
               playPauseButton.setEnabled(false);

               // set the forward button disabled
               forwardButton.setEnabled(false);

               // set stop button enabled
               stopButton.setEnabled(true);

               // set the running time to 0:00
               runningTime.setText(DateUtils.formatElapsedTime(0));

               // set the total time to 0:00
               totalTime.setText(DateUtils.formatElapsedTime(0));

               // set the media player to the start position to avoid
               // wrong restoring state after changes in configuration
               // in special cases
               // mediaPlayer.seekTo(0);

               // show the indeterminate seek bar
               indeterminateSeekBar.setVisibility(View.VISIBLE);

               // set position of the seek bar
               determinateSeekBar.setProgress(0);

               // hide the determinate seek bar
               determinateSeekBar.setVisibility(View.GONE);

               // keep the screen on while recording (recording stops when the
               // screen is off)
               indeterminateSeekBar.setKeepScreenOn(true);
            }
         }
      });

      // set a listener to the rewind button
      rewindButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
            // one second back
            mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 1000);

            // set the seek bar position to the current time of media player
            determinateSeekBar.setProgress(mediaPlayer.getCurrentPosition());

            // set the running time to the current time of media player
            runningTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                  .getCurrentPosition() / 1000));
         }
      });

      // set a listener to the stop button
      stopButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
            // check the recording state
            if (recordingState) {
               // the media recorder is recording

               // so stop to record
               mediaRecorder.stop();
               // state of mediaRecorder: Initial

               // set recording state to not recording (false)
               recordingState = false;

               // reconfigure media player with new data source
               configureMediaPlayer(tempAudioFile.getAbsolutePath());

               // set the record button like not pressed
               recButton.clearColorFilter();

               // restore the music note icon color
               musicNoteIcon.clearColorFilter();

               // set the rewind button enabled
               rewindButton.setEnabled(true);

               // set the play/pause button enabled
               playPauseButton.setEnabled(true);

               // set the forward button enabled
               forwardButton.setEnabled(true);

               // set the running time to 0:00
               runningTime.setText(DateUtils.formatElapsedTime(0));

               // set the total time
               totalTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                     .getDuration() / 1000));

               // show the determinate seek bar
               determinateSeekBar.setVisibility(View.VISIBLE);

               // hide the indeterminate seek bar
               indeterminateSeekBar.setVisibility(View.GONE);

               // don't keep the screen on
               indeterminateSeekBar.setKeepScreenOn(false);

               // set the maximum value of the seekBar
               determinateSeekBar.setMax(mediaPlayer.getDuration());

               // set the seek bar position to the start
               determinateSeekBar.setProgress(0);

               // set the seek bar clickable
               determinateSeekBar.setEnabled(true);

            } else {
               // the media media recorder is not recording

               // stop to play
               mediaPlayer.stop();
               // state of mediaPlayer: Stopped

               try {
                  // (re)prepare the media player
                  mediaPlayer.prepare();
                  // state of mediaPlayer: Prepared

               } catch (IllegalStateException e) {
                  // thrown if media player was in an invalid state
                  e.printStackTrace();

               } catch (IOException e) {
                  // thrown if media player isn't able to read from file
                  e.printStackTrace();
               }
               // state of mediaPlayer: Prepared

               // set the seek bar to the initial position
               determinateSeekBar.setProgress(0);

               // set the running time to the initial position
               runningTime.setText(DateUtils.formatElapsedTime(0));

               // set the "play" icon
               playPauseButton.setImageResource(R.drawable.play);

               // set the default color to play/pause icon
               playPauseButton.clearColorFilter();

               // set default color to music note icon
               musicNoteIcon.clearColorFilter();

            }

            // stop the secondary thread
            secondaryRunCode.toStop();

            // set stop button not enabled
            stopButton.setEnabled(false);
         }
      });

      // set a listener to the play/pause button
      playPauseButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
            // check if media player is playing
            if (mediaPlayer.isPlaying()) {
               // playing

               // pause it
               mediaPlayer.pause();

               // stop the secondary thread
               secondaryRunCode.toStop();

               // set the "play" icon
               playPauseButton.setImageResource(R.drawable.play);

               // set the default color to play/pause icon
               playPauseButton.clearColorFilter();

               // set the default color to music note icon
               musicNoteIcon.clearColorFilter();

            } else {
               // not playing

               // start it
               mediaPlayer.start();

               // make secondaryRunCode executable
               secondaryRunCode.toStart();

               // set the "pause" icon
               playPauseButton.setImageResource(R.drawable.pause);

               // set the color to play/pause icon
               playPauseButton.setColorFilter(Color.GRAY);

               // set the color to music note icon
               musicNoteIcon.setColorFilter(Color.GREEN);

            }

            // set stop button enabled
            stopButton.setEnabled(true);
         }
      });

      // set a listener to the forward button
      forwardButton.setOnClickListener(new OnClickListener() {
         @Override
         public void onClick(View v) {
            // one second forward
            mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + 1000);

            // set the seek bar position to the current time of media player
            determinateSeekBar.setProgress(mediaPlayer.getCurrentPosition());

            // set the running time to the current time of media player
            runningTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                  .getCurrentPosition() / 1000));
         }
      });
   }

   /**
    * This private internal class extends Handler to re-implement the
    * handleMessage() method to receive messages. In particular to update the
    * seek bar, the running time and the total time.
    */
   private class SeekBarHandler extends Handler {
      /**
       * This public method is called when receiving a message. It is
       * responsible of updating the seek bar, the running time and the total
       * time.
       */
      @Override
      public void handleMessage(Message msg) {
         // update the seek bar only if media player is playing
         // or if media recorder is recording

         try {
            if (mediaPlayer.isPlaying()) {
               // media player is playing

               // set the seek bar position to the current time of media player
               determinateSeekBar.setProgress(mediaPlayer.getCurrentPosition());

               // set the running time to the current time of media player
               runningTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                     .getCurrentPosition() / 1000));

            } else if (recordingState) {
               // media recorder is recording

               // set the running time to the current time
               runningTime.setText(DateUtils.formatElapsedTime((System
                     .currentTimeMillis() - startTime) / 1000));

               // set the total time to the current time
               totalTime.setText(DateUtils.formatElapsedTime((System
                     .currentTimeMillis() - startTime) / 1000));
            }

         } catch (IllegalStateException e) {
            // thrown if media player was in an invalid state
            // do nothing to not blocking the application
         }
      }
   }

   /**
    * This private internal class implements Runnable and represents the code to
    * execute on the secondary thread.
    */
   private class SecondaryRunCode implements Runnable {
      /**
       * This private member changes from "false" to "true" when the secondary
       * thread must pause. Call toStart() to set it "false" and toStop() to set
       * it "true";
       */
      private boolean pause = true;

      /**
       * This private member is "true" from the beginning and becomes "false"
       * only when the worker thread must terminate. Call terminate() to set it
       * "false" and terminate the worker thread.
       */
      private boolean run = true;

      /**
       * This public method sets the pause member to "false". Call this before
       * run() or before creating a new thread with this Runnable object.
       */
      synchronized public void toStart() {
         // set pause to false
         pause = false;

         // unblock the worker thread
         notify();
      }

      /**
       * This public method sets the pause member to "true". Call this after
       * starting a new thread to stop it.
       */
      synchronized public void toStop() {
         // set pause to true
         pause = true;
      }

      /**
       * This public method guarantees that worker thread doesn't stay blocked
       * when this activity terminates, by setting the run member to "false".
       * Call this just before destroying it.
       */
      synchronized public void terminate() {
         // set the run to false (otherwise there the
         // possibility that the activity is destroyed
         // when playing and the worker thread cannot
         // terminate)
         run = false;

         // unblock the worker thread
         notify();
      }

      /**
       * This public member get the current value of run.
       */
      synchronized public boolean isRunning() {
         // if the worker thread mustn't run
         if (pause) {
            // then block it
            try {
               wait();

            } catch (InterruptedException e) {
               // thrown if the current thread is already interrupted
               e.printStackTrace();
            }
         }

         // return the value of run
         return run;
      }

      /**
       * The run method is what this Runnable object, and hence this secondary
       * thread, will execute.
       */
      @Override
      public void run() {
         // iterate infinitely
         while (isRunning()) {
            // sleep this thread for 100 milliseconds
            try {
               Thread.sleep(100);

            } catch (InterruptedException e) {
               // thrown if the the current thread is already interrupted
               e.printStackTrace();
            }

            // send a message to the primary thread
            progressHandler.sendEmptyMessage(0);
         }
         // now the worker thread is terminating
      }
   }

   /**
    * This protected method is responsible of restoring some state information
    * after configuration changes occurred. In particular, it resets the
    * position of the media player and the running time to the last ones just
    * before the change event.
    * 
    * @param savedInstanceState
    *           The Bundle object that stores the saved state.
    */
   @Override
   protected void onRestoreInstanceState(Bundle savedInstanceState) {
      // call super onRestoreInstanceState
      super.onRestoreInstanceState(savedInstanceState);

      // check if state information is not null
      if (savedInstanceState != null) {

         // check if media player is not playing
         // and media recorder is not recording
         if (!recordingState && !mediaPlayer.isPlaying()) {

            // get the saved position
            lastPosition = savedInstanceState.getInt("position");

            // set the position to the media player
            mediaPlayer.seekTo(lastPosition);

            // set the running time to the current time of media player
            runningTime.setText(DateUtils
                  .formatElapsedTime(lastPosition / 1000));

            // reset lastPosition for future use
            lastPosition = 0;

         } else if (mediaPlayer.isPlaying()) {
            // if media player is playing

            // set the determinate seek bar enabled
            determinateSeekBar.setEnabled(true);

            // reset the max of the seek bar
            determinateSeekBar.setMax(mediaPlayer.getDuration());

            // set the rewind button enabled
            rewindButton.setEnabled(true);

            // set the play/pause button enabled
            playPauseButton.setEnabled(true);

            // set the forward button enabled
            forwardButton.setEnabled(true);

            // set the total time
            totalTime.setText(DateUtils.formatElapsedTime(mediaPlayer
                  .getDuration() / 1000));
         }
      }
   }

   /**
    * This method is responsible of setting this activity when it becomes
    * visible to the user.
    */
   @Override
   protected void onResume() {
      // call super onResume()
      super.onResume();

      // if state wasn't saved
      if (resumeMediaPlayer) {
         // restore the position
         mediaPlayer.seekTo(lastPosition);

         // set the initial position of the seek bar
         determinateSeekBar.setProgress(lastPosition);

         // if media player position is not equal to the start
         if (lastPosition > 0) {
            // set stop button enabled
            stopButton.setEnabled(true);
         }

         // set the rewind button enabled
         rewindButton.setEnabled(true);

         // set the play/pause button enabled
         playPauseButton.setEnabled(true);

         // set the forward button enabled
         forwardButton.setEnabled(true);

         // set the total time
         totalTime.setText(DateUtils.formatElapsedTime(mediaPlayer
               .getDuration() / 1000));

         // set the running time to the current time of media player
         runningTime.setText(DateUtils.formatElapsedTime(lastPosition / 1000));

         // reset last position variable
         lastPosition = 0;
      }
   }

   /**
    * This protected method is responsible of saving the state of this activity
    * just before the system is going to destroy it. If it is, it is called
    * before onStop() and possibly before onPause().
    * 
    * @param outState
    *           The Bundle to contain information to save.
    */
   @Override
   protected void onSaveInstanceState(Bundle outState) {
      // call the super onSaveInstanceState()
      super.onSaveInstanceState(outState);

      // check if media player is configured
      if (!intent.getAction().equals(Intent.ACTION_INSERT)
            || (tempAudioFile.exists() && tempAudioFile.length() > 0)) {

         // save the current position of the media player
         outState.putInt("position", mediaPlayer.getCurrentPosition());

      } else {
         // if not

         // save the start position
         outState.putInt("position", 0);

      }

      // check if there is the temporary audio file to save
      if (tempAudioFile.exists() && tempAudioFile.length() > 0) {
         // save that was using it
         outState.putBoolean("usingTemp", true);

      } else {
         // save that mediaPlayer was not using it
         outState.putBoolean("usingTemp", false);
      }

      // set no-saved state to false (toast must not be shown)
      noSaved = false;
      
      // save state only if changes in the orientation occurred
      if ((getChangingConfigurations() & ActivityInfo.CONFIG_ORIENTATION) != 0
            || (getChangingConfigurations() & ActivityInfo.CONFIG_LOCALE) != 0) {

         // block the worker thread
         secondaryRunCode.toStop();

         // if media recorder is recording
         if (recordingState) {
            // don't stop to record but declare to retain
            retainMediaRecorder = true;

            // save that mediaRecorder was recording
            outState.putBoolean("isRecording", true);

            // put the start time
            outState.putLong("startTime", startTime);
            
            // save its audio file to be reset
            outState.putBoolean("usingTemp", false);

         } else {
            // declare to not retain
            retainMediaRecorder = false;

            // save that mediaRecorder was not recording
            outState.putBoolean("isRecording", false);

            // if playing
            if (mediaPlayer.isPlaying()) {
               // don't stop to play but declare to retain
               retainMediaPlayer = true;

               // save that mediaPlayer was playing
               outState.putBoolean("isPlaying", true);

            } else {
               // stop to play but declare to retain
               retainMediaPlayer = false;

               // save that mediaPlayer was not playing
               outState.putBoolean("isPlaying", false);
            }
         }

         // set recording state to not recording
         recordingState = false;
      }
   }

   /**
    * This method is responsible of pausing the media player and the media
    * recorder if they are not stopped yet.
    */
   @Override
   protected void onPause() {
      // call the super onPause()
      super.onPause();

      // if media recorder is recording
      if (recordingState) {
         // stop to record
         mediaRecorder.stop();

         // reconfigure media player with new data source
         configureMediaPlayer(tempAudioFile.getAbsolutePath());

         // set the record button like not pressed
         recButton.clearColorFilter();

         // set the rewind button enabled
         rewindButton.setEnabled(true);

         // set the play/pause button enabled
         playPauseButton.setEnabled(true);

         // set the forward button enabled
         forwardButton.setEnabled(true);
      }

      // set the "play" icon
      playPauseButton.setImageResource(R.drawable.play);

      // set the default color to play/pause icon
      playPauseButton.clearColorFilter();

      // set default color to music note icon
      musicNoteIcon.clearColorFilter();

      // block the worker thread
      secondaryRunCode.toStop();

      // check if media player is resumable
      if (!intent.getAction().equals(Intent.ACTION_INSERT)
            || (tempAudioFile.exists() && tempAudioFile.length() > 0)) {

         // if media recorder is recording
         if (retainMediaPlayer || recordingState) {

            // then media player position to save is the start
            lastPosition = 0;

         } else {

            // else save its current position
            lastPosition = mediaPlayer.getCurrentPosition();

         }

         // set recording state to not recording
         recordingState = false;

         // resume the media player on resuming activity
         resumeMediaPlayer = true;
      }
   }

   /**
    * This method is responsible of storing state information of media player
    * when this activity becomes no more visible to the user.
    */
   @Override
   protected void onStop() {
      // call the super onStop()
      super.onStop();

      // remember which source was in play
      if (!retainMediaRecorder && tempAudioFile.exists()
            && tempAudioFile.length() > 0) {

         // save the temporary audio file renaming it so that
         // mediaRecorder.prepare() won't erase it
         tempAudioFile.renameTo(savedAudioFile);

         // source was temporary audio file
         isLastSourceTemp = true;

      } else {
         // source was URI
         isLastSourceTemp = false;
      }

      // if it mustn't be retained
      if (!retainMediaPlayer) {
         // release the media player resources
         mediaPlayer.release();
         // state of mediaPlayer: End
      }

      // if it mustn't be retained
      if (!retainMediaRecorder) {
         // release the media recorder resources
         mediaRecorder.release();
         // state of mediaRecorder: Released
      }
   }

   /**
    * This public method is responsible of retaining the media player or the
    * media recorder when changes in configuration state occur, so that the play
    * or the record actions aren't stopped.
    * 
    * @return The object to retain.
    */
   @Override
   public Object onRetainNonConfigurationInstance() {
      // the object to retain
      final Object media;

      // if the media recorder must be retained
      if (retainMediaRecorder) {
         // object is mediaRecorder
         media = mediaRecorder;

         // else if the media player must be retained
      } else if (retainMediaPlayer) {
         // object is mediaPlayer
         media = mediaPlayer;

         // else no object will be retained
      } else {
         // a null object equals no object
         media = null;
      }

      // return the object to retain
      return media;
   }

   /**
    * This method is responsible of destroying this activity and showing a toast
    * notification if the vocal note isn't saved.
    */
   @Override
   protected void onDestroy() {
      // call super onDestroy()
      super.onDestroy();

      // terminate the worker thread
      secondaryRunCode.terminate();

      // if edited
      if (noSaved) {
         // show a toast indicating the discarding changes
         Toast.makeText(this, R.string.unsaved_note_toast, Toast.LENGTH_SHORT)
               .show();
      }
   }

   /**
    * This method is responsible of preparing the menu to be viewed. It is
    * called when the menu button is pressed on the device.
    * 
    * @param menu
    *           The menu to construct.
    */
   @Override
   public boolean onPrepareOptionsMenu(Menu menu) {
      // if the menu has been already prepared and hence viewed
      if (menu != null) {
         // then clear it
         menu.clear();
      }

      // get a MenuInflater object to construct the menu
      MenuInflater inflater = getMenuInflater();

      // get the action of the current intent
      String action = intent.getAction();

      // switch on the values of the action
      if (action.equals(Intent.ACTION_VIEW)
            && (intent.getType().equals(VocalNotes.NOTES_CONTENT_TYPE_ITEM))) {
         // if viewing an internal vocal note (not imported)

         // create menu items: Delete, Edit and Send
         inflater.inflate(R.menu.note_menu, menu);

      } else {
         // else for all the remaining actions

         // create menu items: Save and Cancel
         inflater.inflate(R.menu.newnote_menu, menu);
      }

      // return preparing from super with the new menu
      return super.onPrepareOptionsMenu(menu);
   }

   /**
    * This method is responsible of executing the proper action for the selected
    * menu item. It is called when an option from the menu is pressed.
    * 
    * @param item
    *           The selected menu item.
    */
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
      // the intent used to start another activity
      Intent i;

      // the builder of the alert dialog
      AlertDialog.Builder builder;

      // switch into the menu items
      switch (item.getItemId()) {
      // case of saving the note
      case R.id.save_newnote:
         // get the title EditText
         EditText titleEditText = (EditText) findViewById(R.id.titletext);

         // get the title
         String title = titleEditText.getText().toString();

         // get the description EditText
         String description = ((EditText) findViewById(R.id.descriptiontext))
               .getText().toString();

         // get the rating RatingBar
         int rating = (int) ((RatingBar) findViewById(R.id.ratingbar))
               .getRating();

         // first condition: title must be not empty
         if (title.length() == 0) {
            // it is empty: notify the user

            // create the builder of the alert dialog
            builder = new AlertDialog.Builder(this);

            // set the proper message
            builder.setMessage(R.string.no_title_saving);

            // set the alert not cancelable
            builder.setCancelable(false);

            // set listener for the positive button
            builder.setPositiveButton(R.string.ok,
                  new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
                        // close the dialog
                        dialog.cancel();
                     }
                  });

            // create the alert dialog from the builder
            AlertDialog alert = builder.create();

            // show the alert dialog
            alert.show();

            // set focus on title EditText
            titleEditText.requestFocus();

         } else {
            // title is not empty

            // construct ContentValues to store into the database
            ContentValues values = new ContentValues();

            // put the title
            values.put(
                  VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_TITLE,
                  title);

            // put the current time and date
            values.put(
                  VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE,
                  System.currentTimeMillis());

            // put the rating
            values.put(
                  VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING,
                  rating);

            // put the description
            values.put(
                  VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DESCRIPTION,
                  description);

            // if this is an imported vocal note (time-stamp not equal to -1)
            if (importedTimeStamp > -1) {
               // put that imported date and time
               values.put(
                     VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_DATE,
                     Long.valueOf(importedTimeStamp));
            }
            // else store current system time

            // check if insert (or import) or update the vocal note
            if (intent.getAction().equals(Intent.ACTION_INSERT)
                  || !intent.getType().equals(
                        VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
               // if it is the case of inserting (or importing) a new vocal note
               // then call insert() (getting the new URI)

               // check if audio is recorded
               if (tempAudioFile.exists() && tempAudioFile.length() > 0) {
                  // then call insert()
                  uri = getContentResolver().insert(
                        VocalNotes.CONTENT_URI_NOTES, values);
               }

            } else {
               // if it is the case of updating an existing vocal note then call
               // update()
               getContentResolver().update(uri, values, null, null);
            }

            // check if temporary audio file exists and is not empty
            if (tempAudioFile.exists() && tempAudioFile.length() > 0) {
               // variable used to store the number of bytes read from temporary
               // audio file
               int n;

               // create a buffer to contain the binary data blocks
               byte[] buffer = new byte[4096];

               // use a FileInputStream for reading from the temporary audio
               // file
               FileInputStream from;

               // use an OutputStream (returned by openOutputStream()) for
               // writing into the storage
               OutputStream to;

               // try to read/write from I/O
               try {
                  // get the input stream
                  from = new FileInputStream(tempAudioFile);

                  // get the output stream
                  to = getContentResolver().openOutputStream(uri);

                  // read from temporary file and write to vocal note audio file
                  // until reaching the EOF
                  while ((n = from.read(buffer)) != -1) {
                     to.write(buffer, 0, n);
                  }

                  // close the input stream
                  from.close();

                  // close the output stream
                  to.close();

               } catch (FileNotFoundException e) {
                  // thrown if temporary audio file doesn0t exists
                  // this event should never occur
                  e.printStackTrace();

               } catch (IOException e) {
                  // thrown if some errors occur while reading or writing into
                  // resources
                  e.printStackTrace();
               }

               // show a toast indicating the successful save
               Toast.makeText(this, R.string.saved_note_toast,
                     Toast.LENGTH_SHORT).show();

               // set no-saved state to false (toast won't be shown)
               noSaved = false;

               // finish
               finish();

            } else {
               // second condition: audio must be recorded for a new (or
               // imported) vocal note
               if (intent.getAction().equals(Intent.ACTION_INSERT)
                     || !intent.getType().equals(
                           VocalNotes.NOTES_CONTENT_TYPE_ITEM)) {
                  // it is not recorded and this is a new vocal note

                  // create the builder of the alert dialog
                  builder = new AlertDialog.Builder(this);

                  // set the proper message
                  builder.setMessage(R.string.no_audio_saving);

                  // set the alert not cancelable
                  builder.setCancelable(false);

                  // set listener for the positive button
                  builder.setPositiveButton(R.string.ok,
                        new DialogInterface.OnClickListener() {
                           public void onClick(DialogInterface dialog, int id) {
                              // close the dialog
                              dialog.cancel();
                           }
                        });

                  // create the alert dialog from the builder
                  AlertDialog alert = builder.create();

                  // show the alert dialog
                  alert.show();

                  // set the focus on the record button
                  recButton.requestFocus();

               } else {
                  // this is an existing vocal note, so notify the user and exit

                  // show a toast indicating the successful save
                  Toast.makeText(this, R.string.saved_note_toast,
                        Toast.LENGTH_SHORT).show();

                  // set no-saved state to false (toast won't be shown)
                  noSaved = false;

                  // finish
                  finish();
               }
            }
         }

         // return true to indicate that this action has been executed
         return true;

         // case of deleting the vocal note
      case R.id.cancel_newnote:
         // create the builder of the alert dialog
         builder = new AlertDialog.Builder(this);

         // set the alert dialog message
         builder.setMessage(R.string.exit_without_saving);

         // set not cancelable
         builder.setCancelable(false);

         // set the positive button action when clicked
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // do not store the note or do not modify it and return to
                     // the previous window.
                     finish();
                  }
               });

         // set the negative button action when clicked
         builder.setNegativeButton(R.string.no,
               new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // return to the edit window (deleting the alert dialog)
                     // and let user complete filling the fields and save the
                     // note
                     dialog.cancel();
                  }
               });

         // create the alert dialog from the builder
         AlertDialog alert = builder.create();

         // show the alert dialog
         alert.show();

         // return true to indicate that this action has been executed
         return true;

         // case of deleting this vocal note
      case R.id.delete:
         // delete this vocal note from the database
         getContentResolver().delete(uri, null, null);

         // notify the user
         Toast.makeText(this, R.string.deleted_note, Toast.LENGTH_SHORT).show();

         // terminate this activity
         finish();

         // return true to indicate that this action has been picked
         return true;

         // case of editing this vocal note
      case R.id.edit:
         // create the intent
         i = new Intent(this, VocalNotesItemActivity.class);

         // set the action to editing
         i.setAction(android.content.Intent.ACTION_EDIT);

         // set the data and its MIME type to be edited
         i.setDataAndType(uri, VocalNotes.NOTES_CONTENT_TYPE_ITEM);

         // start the activity
         startActivity(i);

         // return true to indicate that this action has been picked
         return true;

         // case of sending this vocal note
      case R.id.send:
         // create the intent for sending this vocal note
         i = new Intent(Intent.ACTION_SEND);

         // set the MIME type of the data to be send
         i.setType("application/json");

         try {
            // create the JSON file with this vocal notes URI
            VocalNotes.createJsonFile(getContentResolver(), uri);

            // put the JSON file URI to be send
            i.putExtra(Intent.EXTRA_STREAM,
                  VocalNotes.getJsonURI(getContentResolver()));

         } catch (FileNotFoundException f) {
            // thrown if URI is wrong or cannot write into the JSON file
            // also if the URI corresponds to many items
            f.printStackTrace();

         }

         // create the chooser from this intent to let the user choose
         // its preferred method to share this vocal note
         i = Intent.createChooser(i,
               getResources().getText(R.string.send_action));

         // start the activity for sending
         startActivity(i);

         // return true to indicate that this action has been picked
         return true;

         // default case
      default:
         // unknown actions are picked by the super class
         return super.onOptionsItemSelected(item);
      }
   }
}
